package frame.laxcen.util;

import frame.laxcen.request.IRequest;
import frame.util.ByteArrayUtil;
import frame.util.ByteUtil;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class FrameWriter {

    private byte frameHead, messageType, mid;

    private List<Byte> paramList = new ArrayList<>();

    private FrameWriter(byte messageType, byte mid) {

        this.frameHead = (byte) 0xaa;

        this.messageType = messageType;

        this.mid = mid;
    }

    public static FrameWriter instance(byte messageType, byte mid) {

        return new FrameWriter(messageType, mid);
    }

    public static <T extends IRequest> FrameWriter instance(Class<T> requestType) {

        String[] part = requestType.getName().split("_");

        return new FrameWriter(Integer.valueOf(part[1], 2).byteValue(), Integer.valueOf(part[2], 16).byteValue());
    }

    public byte[] toByteArray() {

        byte[] tmp = new byte[] { frameHead, messageType, mid };

        if(paramList.size() > 0) {

            tmp = ByteArrayUtil.concat(tmp, ByteUtil.shortToBytes((short) paramList.size()));

            tmp = ByteArrayUtil.concat(tmp, ByteArrayUtil.unBoxed(paramList.toArray(new Byte[] { })));

        } else {

            tmp = ByteArrayUtil.concat(tmp, ByteUtil.shortToBytes((short) 0));
        }

        return ByteArrayUtil.concat(tmp, Algorithm.getCRC(Arrays.copyOfRange(tmp, 1, tmp.length)));
    }

    private void writePid(byte pid) {

        writeU8(pid);
    }

    private void writeLength(int length) {

        writeU16((short) length);
    }

    public FrameWriter writeU8(byte b) {

        paramList.add(b);

        return this;
    }

    public FrameWriter writeU8s(byte[] bs) {

        writeLength(bs.length);

        for(byte b : bs) paramList.add(b);

        return this;
    }

    public FrameWriter writeU8v(byte ... bs) {

        writeLength(bs.length);

        for(byte b : bs) paramList.add(b);

        return this;
    }

    public FrameWriter writeU16(short s) {

        for(byte b : ByteUtil.shortToBytes(s)) paramList.add(b);

        return this;
    }

    public FrameWriter writeU16s(short[] ss) {

        writeLength(ss.length);

        for(short s : ss) writeU16(s);

        return this;
    }

    public FrameWriter writeU16v(short ... ss) {

        writeLength(ss.length);

        for(short s : ss) writeU16(s);

        return this;
    }

    public FrameWriter writeU32(int i) {

        for(byte b : ByteUtil.intToBytes(i)) paramList.add(b);

        return this;
    }

    public FrameWriter writeU32s(int[] is) {

        writeLength(is.length);

        for(int i : is) writeU32(i);

        return this;
    }

    public FrameWriter writeU32v(int ... is) {

        writeLength(is.length);

        for(int i : is) writeU32(i);

        return this;
    }

    public FrameWriter writeP8(byte pid, byte b) {

        writePid(pid);

        paramList.add(b);

        return this;
    }

    public FrameWriter writeP8s(byte pid, byte[] bs) {

        writePid(pid);

        writeLength(bs.length);

        for(byte b : bs) paramList.add(b);

        return this;
    }

    public FrameWriter writeP8v(byte pid, byte ... bs) {

        writePid(pid);

        writeLength(bs.length);

        for(byte b : bs) paramList.add(b);

        return this;
    }

    public FrameWriter writeP16(byte pid, short s) {

        writePid(pid);

        writeU16(s);

        return this;
    }

    public FrameWriter writeP16s(byte pid, short[] ss) {

        writePid(pid);

        writeLength(ss.length);

        for(short s : ss) writeU16(s);

        return this;
    }

    public FrameWriter writeP16v(byte pid, short ... ss) {

        writePid(pid);

        writeLength(ss.length);

        for(short s : ss) writeU16(s);

        return this;
    }

    public FrameWriter writeP32(byte pid, int i) {

        writePid(pid);

        writeU32(i);

        return this;
    }

    public FrameWriter writeP32s(byte pid, int[] is) {

        writePid(pid);

        writeLength(is.length);

        for(int i : is) writeU32(i);

        return this;
    }

    public FrameWriter writeP32v(byte pid, int ... is) {

        writePid(pid);

        writeLength(is.length);

        for(int i : is) writeU32(i);

        return this;
    }
}
